﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;
using QTPlugin;
using QTPlugin.Interop;
using Kawanon.QtHoney;
using Microsoft.Win32;
using System.Windows.Forms;
using _IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.Runtime.InteropServices;
using System.Text;
using System.Runtime.Serialization;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms.Design;
using Kawanon.Foundation;

namespace Kawanon.Usability
{
	public class ActionEditor : UITypeEditor
	{
		public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
		{
			IList<GestureExpression> exprs = GestureSettings.LoadAllExpressions();

			List<Action> actions = new List<Action>();
			foreach (GestureExpression expr in exprs)
			{
				actions.Add(expr.Action);
			}

			ChooseFromArrayForm form = new ChooseFromArrayForm();
			form.Source = actions.ToArray();
			//form.Stringizer = delegate(object obj)
			//{
			//    Action a = (Action)obj;
			//    return a.Name;
			//};

			if (form.ShowDialog() == DialogResult.Cancel)
			{
				return value;
			}
			Action action = (Action)form.Selected;
			return action;
		}

		public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
		{
			return UITypeEditorEditStyle.Modal;
		}
	}

	public class PopupMenuFactory
	{
		private delegate void Generator(ToolStripItemCollection items);

		public IPluginServer Server { get; set; }

		private Dictionary<PopupMenus, Generator> m_gens = new Dictionary<PopupMenus, Generator>();

		public PopupMenuFactory()
		{
			m_gens.Add(PopupMenus.Group, SelectFirst(ForGroup));
			m_gens.Add(PopupMenus.History, ForHistory);
			m_gens.Add(PopupMenus.Tabs, ForTabs);
		}

		private static Generator SelectFirst(Generator gen)
		{
			return delegate(ToolStripItemCollection items)
			{
				int origin = items.Count;

				gen(items);

				if (items.Count > origin)
				{
					items[origin].Select();
				}
			};
		}

		private ToolStripMenuItem CreateAddressItem(Address addr)
		{
			byte[] bytes = Utility.PidlFromAddress(addr);
			string dispName = Utility.GetDisplayName(bytes);
			Bitmap bitmap = Utility.GetIconBitmap(bytes, false);
			ToolStripMenuItem item = new ToolStripMenuItem(dispName, bitmap);
			return item;
		}

		private void ForTabs(ToolStripItemCollection items)
		{
			if (Server == null)
			{
				return;
			}
			ITab[] tabs = Server.GetTabs();
			Array.ForEach(tabs, delegate(ITab tab)	// foreach は tab が最後の要素になるので
			{
				ToolStripMenuItem item = CreateAddressItem(tab.Address);
				item.Click += Utility.ToEventHandler(delegate()
				{
					Server.SelectedTab = tab;
				});
				items.Add(item);

				if (tab.Selected)
				{
					item.Select();
					item.Font = new Font(item.Font, FontStyle.Bold);
				}
			});
		}

		private void ForHistory(ToolStripItemCollection items)
		{
			if (Server == null || Server.SelectedTab == null)
			{
				return;
			}
			ITab tab = Server.SelectedTab;
			List<Address> addrs = new List<Address>();
			addrs.AddRange(tab.GetHistory(true));

			int selectedIndex = addrs.Count;

			addrs.Add(Server.SelectedTab.Address);
			addrs.AddRange(tab.GetHistory(false));

			int origin = items.Count;

			foreach (Address addr in addrs)
			{
				int index = items.Count;

				ToolStripMenuItem item = CreateAddressItem(addr);
				item.Click += Utility.ToEventHandler(delegate()
				{
					int diff = index - selectedIndex;
					if (diff > 0)
					{
						Server.ExecuteCommand(Commands.GoForward, diff);
					}
					else if (diff < 0)
					{
						Server.ExecuteCommand(Commands.GoBack, -diff);
					}
				});
				items.Add(item);

				if (index == origin + selectedIndex)
				{
					item.Select();
					item.Font = new Font(item.Font, FontStyle.Bold);
				}
			}
		}

		private void ForGroup(ToolStripItemCollection items)
		{
			GroupMenuFactory factory = new GroupMenuFactory();
			factory.Expandable = false;
			factory.Server = Server;
			items.AddRange(factory.CreateItems());
		}

		public void CreateItems(ToolStripItemCollection items, PopupMenus menu)
		{
			Generator gen;
			if (!m_gens.TryGetValue(menu, out gen))
			{
				return;
			}

			gen(items);
		}
	}

	//public abstract class PopupMenu
	//{
	//    public abstract ContextMenuStrip CreateInstance();
	//}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	public enum PopupMenus
	{
		[EnumDisplayName("なし")]
		None,

		[EnumDisplayName("タブ一覧")]
		Tabs,

		[EnumDisplayName("戻る/進む")]
		History,

		[EnumDisplayName("グループ")]
		Group,
	}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	[Flags]
	public enum WheelDirection
	{
		[EnumDisplayName("なし")]
		None = 0,

		[EnumDisplayName("↑")]
		Up = 1,

		[EnumDisplayName("↓")]
		Down = 2,

		[EnumDisplayName("両方")]
		Both = Up | Down,
	}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	public enum TriggerButtons
	{
		[EnumDisplayName("なし")]
		None = MouseButtons.None,

		[EnumDisplayName("左ボタン")]
		Left = MouseButtons.Left,

		[EnumDisplayName("中央ボタン")]
		Middle = MouseButtons.Middle,

		[EnumDisplayName("右ボタン")]
		Right = MouseButtons.Right,

		[EnumDisplayName("X1ボタン")]
		XButton1 = MouseButtons.XButton1,

		[EnumDisplayName("X2ボタン")]
		XButton2 = MouseButtons.XButton2,
	}

	public sealed class WheelEventArgs : GestureEventArgs
	{
		public TriggerButtons Button { get; private set; }
		public WheelDirection Direction { get; private set; }

		public WheelEventArgs(TriggerButtons button, WheelDirection direction, GestureExpression expr) : base(expr)
		{
			Button = button;
			Direction = direction;
		}
	}

	public delegate void WheelEventHandler(object sender, WheelEventArgs e);

	[Serializable]
	public class WheelExpression
	{
		private PopupMenus m_menu;
		private Action m_action;

		[Category("ジェスチャ")]
		[DisplayName("ボタン")]
		//[DefaultValue(TriggerButtons.Right)]
		public TriggerButtons Button { get; set; }

		[Category("ジェスチャ")]
		[DisplayName("ホイール")]
		//[DefaultValue(WheelDirection.Both)]
		public WheelDirection Direction { get; set; }

		[Category("動作")]
		[DisplayName("メニュー")]
		//[DefaultValue(PopupMenus.Tabs)]
		public PopupMenus Menu
		{
			get
			{
				return m_menu;
			}
			set
			{
				if (value != PopupMenus.None)
				{
					m_action = null;
				}
				m_menu = value;
			}
		}

		[Category("動作")]
		[DisplayName("アクション")]
		[Editor(typeof(ActionEditor), typeof(UITypeEditor))]
		//[DefaultValue(null)]
		public Action Action
		{
			get
			{
				return m_action;
			}
			set
			{
				if (value != null)
				{
					m_menu = PopupMenus.None;
				}
				m_action = value;
			}
		}

		public WheelExpression()
		{
			Button = TriggerButtons.Right;
			Direction = WheelDirection.Both;
			Menu = PopupMenus.Tabs;
		}

		public WheelExpression(TriggerButtons button, WheelDirection direction, PopupMenus menu)
		{
			Button = button;
			Direction = direction;
			Menu = menu;
		}

		public override string ToString()
		{
			string behavior = String.Empty;
			if (Menu != PopupMenus.None)
			{
				behavior = Utility.GetEnumDisplayName(Menu);
			}
			else if (Action != null)
			{
				behavior = Action.Name;
			}

			return String.Format("{0}({1}) {2}",
				Utility.GetEnumDisplayName(Button), Utility.GetEnumDisplayName(Direction), behavior).Trim();
		}
	}

	// LocalSerializationBinder でジェネリック型は解決しにくいので、明示的にアセンブリ内で型を作る
	[Serializable]
	public class WheelExpressionCollection : List<WheelExpression>
	{
	}

	[Serializable]
	public class GestureInformation
	{
		private const int DEFAULT_MINIMUM_DISTANCE = 10;

		private WheelExpressionCollection m_wheelExpressions = null;

		private int m_minimumDistance;

		[Browsable(false)]
		public bool Enabled { get; set; }

		[DisplayName("ジェスチャの最低距離 (px)")]
		[Description("マウスジェスチャを検知する際の最小値を指定します。")]
		[Category("全般")]
		[DefaultValue(DEFAULT_MINIMUM_DISTANCE)]
		public int MinimumDistance
		{
			get
			{
				if (m_minimumDistance <= 0)
				{
					return DEFAULT_MINIMUM_DISTANCE;
				}
				return m_minimumDistance;
			}
			set
			{
				if (value > 0)
				{
					m_minimumDistance = value;
				}
				else
				{
					throw new ArgumentException();
				}
			}
		}

		[Browsable(false)]
		public bool DrawTrails { get; set; }

		[Browsable(false)]
		public bool PreferGestureTrack { get; set; }

		[DisplayName("メニューとアクションの割り当て")]
		[Description("ホイールジェスチャ（マウスボタンを押しながらホイール）に任意のメニューまたはアクションを設定します。右端のボタンから設定できます。")]
		[Category("ホイールジェスチャ")]
		[Editor(typeof(MyCollectionEditor), typeof(UITypeEditor))]
		[TypeConverter(typeof(MyCollectionConverter))]
		[DefaultValue(null)]
		public WheelExpressionCollection WheelExpressions
		{
			get
			{
				if (m_wheelExpressions == null)
				{
					m_wheelExpressions = new WheelExpressionCollection();
					// デフォルト定義
					m_wheelExpressions.Add(
						new WheelExpression(TriggerButtons.Right, WheelDirection.Up, PopupMenus.History));
					m_wheelExpressions.Add(
						new WheelExpression(TriggerButtons.Right, WheelDirection.Down, PopupMenus.Tabs));
				}
				return m_wheelExpressions;
			}
			set
			{
				m_wheelExpressions = value;
			}
		}

		public GestureInformation()
		{
			MinimumDistance = DEFAULT_MINIMUM_DISTANCE;
			Enabled = true;
			DrawTrails = true;
			PreferGestureTrack = false;
			WheelExpressions = null;
		}
	}

	public class GestureDescription
	{
		public string Category { get; private set; }
		public bool IsAcceptTarget { get; private set; }

		public GestureDescription(string category, bool isAcceptTarget)
		{
			Category = category;
			IsAcceptTarget = isAcceptTarget;
		}
	}

	public class GestureEventArgs : EventArgs
	{
		public GestureExpression Expression { get; private set; }
		public IList<GestureExpression> Suggestions { get; private set; }

		public GestureEventArgs(GestureExpression expr)
		{
			Expression = expr;
		}

		public GestureEventArgs(GestureExpression expr, IList<GestureExpression> suggestions)
		{
			Expression = expr;
			Suggestions = suggestions;
		}
	}

	public delegate void GestureEventHandler(object sender, GestureEventArgs e);

	[Serializable]
	public abstract class Action
	{
		public class Arguments
		{
			public IPluginServer Server { get; set; }
			public IPluginClient Plugin { get; set; }
			public IShellView ShellView { get; set; }
			public IntPtr ViewHandle { get; set; }
			public object Target { get; set; }
			// ホイールジェスチャの方向によって逆の動作をする
			public bool Reverse { get; set; }

			// コマンドの実行後も選択を保持する
			public bool KeepSelection { get; set; }
		}

		protected Address[] GetAddresses(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetAddresses(data);
			}
			Address[] addrs = target as Address[];
			if (addrs != null)
			{
				return addrs;
			}
			return null;
		}

		protected string[] GetFileNames(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetFileNames(data);
			}
			string[] paths = target as string[];
			if (paths != null)
			{
				return paths;
			}
			return null;
		}

		public abstract string Name { get; }

		public abstract void Execute(Arguments args);

		//protected Action(string name)
		//{
		//    Name = name;
		//}

		public override bool Equals(object obj)
		{
			if (!(obj is Action))
			{
				return false;
			}
			//NOTE: 各クラスで GetHashCode にタイプのハッシュを含めるので不要
			//if (this.GetType() != obj.GetType())
			//{
			//    return false;
			//}
			if (GetHashCode() != obj.GetHashCode())
			{
				return false;
			}

			return true;
		}

		public override int GetHashCode()
		{
			//デフォルトでは同じ型は同じハッシュで
			return ~ this.GetType().GetHashCode();
		}

		//protected int InternalGetHashCode()
		//{
		//    return base.GetHashCode();
		//}

		public override string ToString()
		{
			return this.Name;
		}

		public static Action From(Commands command, object arg)
		{
			return new BuiltInCommand(command, arg);
		}

		public static Action From(Commands command)
		{
			return new BuiltInCommand(command, null);
		}
	}

	[Serializable]
	public abstract class CustomAction : Action
	{
		private static readonly Random rand = new Random();

		public int Id { get; private set; }

		public virtual bool IsAcceptTarget { get { return false; } }

		public abstract DialogResult ShowEditDialog(IntPtr hwnd);

		public CustomAction()
		{
			Id = rand.Next(Int32.MinValue, Int32.MaxValue);
		}

		//public override int GetHashCode()
		//{
		//    throw new NotSupportedException();
		//}

		public override int GetHashCode()
		{
			//NOTE: ShowEditDialog によって内容が変化するため、同一性をIDとして保持
			return Id;
		}

		protected DialogResult ShowPropertiesForm<T>(IntPtr hwnd, string title, Bitmap image, ref T obj, Predicate<T> validator)
		{
			PropertiesForm form = new PropertiesForm();
			form.Text = title;
			if (image != null)
			{
				form.SetIcon(image);
			}
			form.SelectedObject = obj;
			if (form.ShowDialog(Form.FromHandle(hwnd)) == DialogResult.Cancel)
			{
				return DialogResult.Cancel;
			}
			T ret = (T)form.SelectedObject;
			if (!validator(ret))
			{
				return DialogResult.Cancel;
			}

			obj = ret;	// 値を反映
			return DialogResult.OK;
		}
	}

	[Serializable]
	internal class BuiltInCommand : Action
	{
		//[DllImport("shell32.dll")]
		//[return: MarshalAs(UnmanagedType.Bool)]
		//public static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);

		//private delegate object TargetTranslator(BuiltInCommand cmd, Action.Arguments args);
		private delegate bool Injector(BuiltInCommand cmd, Action.Arguments args);

		private static Dictionary<Commands, string> s_names = new Dictionary<Commands, string>();
		//private static Dictionary<Commands, TargetTranslator> s_translators = new Dictionary<Commands, TargetTranslator>();
		private static Dictionary<Commands, Injector> s_injectors = new Dictionary<Commands, Injector>();


		static BuiltInCommand()
		{
			// 0.0.1
			s_names.Add(Commands.GoBack, "戻る");
			s_names.Add(Commands.GoForward, "進む");
			s_names.Add(Commands.GoUpOneLevel, "上へ");
			s_names.Add(Commands.RefreshBrowser, "更新");
			s_names.Add(Commands.CloseCurrentTab, "現在のタブを閉じる");
			s_names.Add(Commands.CloseLeft, "左を閉じる");
			s_names.Add(Commands.CloseRight, "右を閉じる");
			s_names.Add(Commands.CloseAllButCurrent, "現在のタブ以外を閉じる");
			s_names.Add(Commands.CloseWindow, "ウィンドウを閉じる");
			s_names.Add(Commands.UndoClose, "閉じたタブを元に戻す");
			s_names.Add(Commands.ToggleTopMost, "最前面表示のON/OFF");
			s_names.Add(Commands.OpenTabBarOptionDialog, "オプションを開く");
			s_names.Add(Commands.OpenButtonBarOptionDialog, "ツールバーのカスタマイズ");

			//NOTE: 過去の保存値とハッシュが変わってしまうので使わない
			//s_translators.Add(Commands.GoBack, TargetIsOne);
			//s_translators.Add(Commands.GoForward, TargetIsOne);

			// 0.0.2
			s_names.Add(Commands.BrowseFolder, "フォルダを指定して開く");
			s_names.Add(Commands.MD5, "MD5ハッシュを計算");
			s_names.Add(Commands.ReorderTabsByName, "名前で並び替え");
			s_names.Add(Commands.ReorderTabsByPath, "パスで並び替え");
			s_names.Add(Commands.ReorderTabsByActv, "アクティブ順で並び替え");
			s_names.Add(Commands.ReorderTabsRevers, "逆順で並び替え");

			s_injectors.Add(Commands.GoBack, Injector_GoBack);
			s_injectors.Add(Commands.GoForward, Injector_GoForward);
			s_injectors.Add(Commands.CloseCurrentTab, Injector_CloseCurrentTab);
			s_injectors.Add(Commands.MD5, Injector_TargetIsStringArray);
		}

		private static bool Injector_GoBack(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(args.Reverse ? Commands.GoForward : Commands.GoBack, cmd.m_arg);
			return true;
		}

		private static bool Injector_GoForward(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(args.Reverse ? Commands.GoBack : Commands.GoForward, cmd.m_arg);
			return true;
		}

		private static bool Injector_CloseCurrentTab(BuiltInCommand cmd, Action.Arguments args)
		{
			ITab[] tabs = args.Server.GetTabs();
			if (tabs.Length == 1)
			{
				args.Server.ExecuteCommand(Commands.CloseWindow, null);
				return true;
			}
			else
			{
				return false;
			}
		}

		private static bool Injector_TargetIsStringArray(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(cmd.m_command, cmd.GetFileNames(args.Target));
			return true;
		}

		//private static object TargetIsOne(object target)
		//{
		//    return 1;
		//}

		//private static object TargetToAddressArray(BuiltInCommand cmd, Action.Arguments args)
		//{
		//    return cmd.GetAddresses(args.Target);
		//}

		[NonSerialized]
		private string m_name = null;

		private Commands m_command;
		private object m_arg;	// 固定パラメータ

		public override string Name
		{
			get
			{
				if (m_name == null)
				{
					//NOTE: シリアライズ対策のため、ここで初期化
					m_name = s_names[m_command];
				}
				return m_name;
			}
		}

		public BuiltInCommand(Commands command, object arg)
		{
			m_command = command;
			m_arg = arg;
		}

		public override void Execute(Arguments args)
		{
			//object arg = m_arg;
			//if (arg == null)
			//{
			//    TargetTranslator translator = null;
			//    if (s_translators.TryGetValue(m_command, out translator))
			//    {
			//        arg = translator.Invoke(this, args);
			//    }
			//}

			Injector injector = null;
			if (s_injectors.TryGetValue(m_command, out injector))
			{
				if (injector(this, args))
				{
					return;
				}
			}
			args.Server.ExecuteCommand(m_command, m_arg);
		}

		public override int GetHashCode()
		{
			//NOTE: enum のハッシュは0からのインデックスなので、m_argとは重みを変える
			return GetType().GetHashCode() ^ (m_command.GetHashCode() << 16) ^ (m_arg != null ? m_arg.GetHashCode() : 0);
		}
	}

	[Serializable]
	public class GestureExpression
	{
		public static readonly int[] EmptyTrails = new int[0];

		private static readonly Keys[] s_supportedKeys = new Keys[] {
			Keys.RButton, Keys.MButton, Keys.XButton1, Keys.XButton2 };

		public static IList<Keys> GetSupportedButtons()
		{
			return Array.AsReadOnly(s_supportedKeys);
		}

		public virtual string Name
		{
			get
			{
				if (Action != null)
				{
					return Action.Name;
				}
				else
				{
					return null;
				}
			}
		}

		public int[] Trails { get; private set; }
		public Action Action { get; private set; }
		public Keys Button
		{
			get
			{
				if (m_button == Keys.None)	// 旧バージョンとの互換性のため
				{
					return Keys.RButton;
				}
				return m_button;
			}
			private set
			{
				if (Array.IndexOf(s_supportedKeys, value) == -1)
				{
					throw new ArgumentException();
				}
				m_button = value;
			}
		}

		[OptionalField]
		private Keys m_button;

		public GestureExpression(int[] trails, Action action, Keys button)
		{
			Trails = trails;
			Action = action;
			Button = button;
		}

		public GestureExpression(string urdl, Action action, Keys button)
			: this(Parse(urdl), action, button)
		{
		}

		public GestureExpression(int[] trails, Action action)
			: this(trails, action, Keys.RButton)
		{
		}

		public GestureExpression(string urdl, Action action)
			: this(Parse(urdl), action, Keys.RButton)
		{
		}

		public GestureExpression(int[] trails, GestureExpression expr)
			:this(trails, expr.Action, expr.Button)
		{
		}

		public GestureExpression(string urdl, GestureExpression expr)
			: this(Parse(urdl), expr.Action, expr.Button)
		{
		}

		public GestureExpression(Action action, GestureExpression expr)
			: this(expr.Trails, action, expr.Button)
		{
		}

		private static int[] Parse(string urdl)
		{
			//if (urdl.Length == 0)
			//{
			//    throw new FormatException("string is empty");
			//}
			int[] trails = Array.ConvertAll(urdl.ToCharArray(), new Converter<char, int>(
				delegate(char c)
				{
					switch (Char.ToUpper(c))
					{
						case 'U':
						case '↑':
							return 0;
						case 'R':
						case '→':
							return 1;
						case 'D':
						case '↓':
							return 2;
						case '←':
						case 'L':
							return 3;
					}
					throw new FormatException(c + " of " + urdl);
				}));

			return trails;
		}

		public static string FormatTrails(int[] trails, string urdl)
		{
			if (trails == null)
			{
				return String.Empty;
			}
			char[] ar = Array.ConvertAll(trails, new Converter<int, char>(
				delegate(int direction)
				{
					return urdl[direction];
				}));

			return new String(ar);
		}

		public static string FormatTrails(int[] trails)
		{
			return FormatTrails(trails, "↑→↓←");
		}

		public string FormatTrails(string urdl)
		{
			return FormatTrails(Trails, urdl);
		}

		public string FormatTrails()
		{
			return FormatTrails(Trails);
		}

		public bool TrailsEquals(int[] trails)
		{
			if (Trails.Length != trails.Length)
			{
				return false;
			}
			for (int i = 0; i < Trails.Length; i++)
			{
				if (Trails[i] != trails[i])
				{
					return false;
				}
			}
			return true;
		}

		public bool TrailsEquals(GestureExpression expr)
		{
			return TrailsEquals(expr.Trails);
		}

		public override string ToString()
		{
			return String.Format("{0} [{1}]", Name, FormatTrails());
		}
	}
}
